import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

public class Bank implements AutoCloseable {

    private final Map<String, Account> accounts = new ConcurrentHashMap<>();
    private final Random random = new Random();

    private final ExecutorService fraudService = Executors.newSingleThreadExecutor();
    private final AtomicLong transferCount = new AtomicLong();

    public synchronized boolean isFraud(String fromAccountNum, String toAccountNum, long amount)
        throws InterruptedException
    {
        if (accounts.get(fromAccountNum).isLocked() && accounts.get(toAccountNum).isLocked()) {
            return true;
        }

        Thread.sleep(1000);
        return random.nextBoolean();
    }

    /**
     * TODO: реализовать метод. Метод переводит деньги между счетами. Если сумма транзакции > 50000,
     * то после совершения транзакции, она отправляется на проверку Службе Безопасности – вызывается
     * метод isFraud. Если возвращается true, то делается блокировка счетов (как – на ваше
     * усмотрение)
     */
    public void transfer(String fromAccountNum, String toAccountNum, long amount) throws TransferException {
        if (fraudService.isShutdown()) {
            throw new TransferException("Bank is closed");
        }
        if (amount <= 0) {
            throw new TransferException("Wrong transfer amount: " + amount);
        }

        Account fromAccount = accounts.get(fromAccountNum);
        if (fromAccount == null) {
            throw new TransferException("There is no source account in the bank: " + fromAccountNum);
        }
        if (fromAccount.isLocked()) {
            throw new TransferException("Source account is locked: " + fromAccountNum);
        }

        Account toAccount = accounts.get(toAccountNum);
        if (toAccount == null) {
            throw new TransferException("There is no target account in the bank: " + toAccountNum);
        }
        if (toAccount.isLocked()) {
            throw new TransferException("Target account is locked: " + toAccountNum);
        }

        if (!fromAccount.addMoney(-amount)) {
            throw new TransferException("Transfer amount exceeds source account: " + amount);
        }
        toAccount.addMoney(amount);

        transferCount.incrementAndGet();

        if (amount > 50000) {
            fraudService.submit(new FraudThread(
                            this,
                            fromAccount,
                            toAccount,
                            amount
                    )
            );
        }
    }

    /**
     * TODO: реализовать метод. Возвращает остаток на счёте.
     */
    public long getBalance(String accountNum) {
        return accounts.get(accountNum).getMoney();
    }

    public long getSumAllAccounts() {
        Optional<Long> sum = accounts.values().stream()
                .map(Account::getMoney)
                .reduce(Long::sum);

        return sum.isPresent() ? sum.get() : 0;
    }

    public void addAccount(Account account) {
        this.accounts.put(account.getAccNumber(), account);
    }

    public long getTransferCount() {
        return transferCount.get();
    }

    @Override
    public void close() throws InterruptedException {
        fraudService.shutdown();
        fraudService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
    }
}
